﻿using Exiled.API.Extensions;
using Exiled.API.Features;
using Exiled.Events.EventArgs.Interfaces;
using Hints;
using PlayerRoles;
using RgCore.GameData;
using RgCore.Menu;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEngine;
using VoiceChat;

namespace RgCore.API
{
    /// <summary>
    /// 冷却模块，需要有一个实例载体
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public struct CooldownStatus<T> where T : class
    {
        public CooldownStatus(uint maxcooldown, InstanceConditionResponse<T> checker = null)
        {
            MaxCooldown = maxcooldown;
            Condition = checker;
            LastActivation = DateTime.MinValue;
            ActivatingDeadLine = DateTime.Now;
            DeactivationFinished = true;
            if (checker == null)
            {
                Condition = (T _, out string r) => { r = ""; return true; };
            }
        }

        public enum CdStatus
        {
            Ready,
            Cooling,
            Activating,
            LongActivating,
            Unavailable,
        }

        /// <summary>
        /// 截止激活时间
        /// </summary>
        public DateTime ActivatingDeadLine;

        /// <summary>
        /// 检测条件（可选）
        /// </summary>
        public readonly InstanceConditionResponse<T> Condition;

        /// <summary>
        /// 最后一次触发时间
        /// </summary>
        public DateTime LastActivation;

        /// <summary>
        /// 最大冷却
        /// </summary>
        public uint MaxCooldown;

        public bool DeactivationFinished;

        public int RemainingActivation => ActivatingDeadLine <= DateTime.Now ? 0 : (int)Math.Round((ActivatingDeadLine - DateTime.Now).TotalSeconds);

        public int RemainingDuration => LastActivation.AddSeconds(MaxCooldown) <= DateTime.Now ? 0 : (int)Math.Round(MaxCooldown - (DateTime.Now - LastActivation).TotalSeconds);

        public CdStatus GetStatus(T instance) 
        {
            if (Status != CdStatus.Ready)
                return Status;
            return !Condition(instance, out _) ? CdStatus.Unavailable : CdStatus.Ready;
        }

        public CdStatus Status
        {
            get
            {
                if (ActivatingDeadLine > DateTime.Now || ActivatingDeadLine == DateTime.MinValue)
                    return CdStatus.Activating;
                if (RemainingDuration > 0)
                    return CdStatus.Cooling;
                return CdStatus.Ready;
            }
        }

        public void AddActivateDuration(int duration)
        {
            if (Status == CdStatus.Activating)
            {
                ActivatingDeadLine = ActivatingDeadLine.AddSeconds(duration);
            }
            else if (Status != CdStatus.LongActivating && duration > 0)
            {
                Activate((uint)duration);
            }
        }

        public string GetText(bool icon)
        {
            switch (Status)
            {
                case CdStatus.Ready:
                    return $"<color=#00FF00>{(icon ? "✔" : "已就绪")}</color>";

                case CdStatus.Cooling:
                    return $"<color=#FF6A6A>{RemainingDuration}s</color>";

                case CdStatus.Activating:
                    return $"<color=#00FFFF>{RemainingActivation}s</color>";

                case CdStatus.LongActivating:
                    return $"<color=#00FFFF>{(icon ? "▶" : "激活")}</color>";
            }
            return "?";
        }

        public string GetText(T instance, bool icon)
        {
            var cd = GetStatus(instance);
            switch (cd)
            {
                case CdStatus.Ready:
                    return $"<color=#00FF00>{(icon ? "✔" : "已就绪")}</color>";

                case CdStatus.Cooling:
                    return $"<color=#FF6A6A>{RemainingDuration}s</color>";

                case CdStatus.Activating:
                    return $"<color=#00FFFF>{RemainingActivation}s</color>";

                case CdStatus.LongActivating | CdStatus.Activating:
                    return $"<color=#00FFFF>{(icon ? "▶" : "激活")}</color>";

                case CdStatus.Unavailable:
                    return $"<color=#B22222>{(icon ? "✖" : "不可用")}</color>";
            }
            return cd.ToString();
        }

        public void Activate(uint duration)
        {
            DeactivationFinished = false;
            ActivatingDeadLine = duration == 0 ? DateTime.MinValue : DateTime.Now.AddSeconds(duration);
        }

        public void Deactivate()
        {
            ActivatingDeadLine = DateTime.Now;
        }

        public void StartCD()
        {
            LastActivation = DateTime.Now;
        }
    }

    public static class AllEventsInvoker
    {
        public static readonly string[] BannedEvent = new string[]
        {
           "PreAuthenticating",
           "Kicking",
           "Kicked",
           "Banning",
           "Banned",
           "Joined",
           "Verified",
           "VoiceChatting",
           "Transmitting",
           "MakingNoise"
        };

        public static event Exiled.Events.Features.CustomEventHandler<IExiledEvent> AnyEventCalled;

        public static void RegisterAllEvent()
        {
            List<EventInfo> events = new List<EventInfo>();
            foreach (Type type in Assembly.GetAssembly(typeof(Exiled.Events.Events)).GetTypes())
                events.AddRange(type.GetEvents());
            foreach (EventInfo ei in events)
            {
                try
                {
                    if (!BannedEvent.Contains(ei.Name))
                    {
                        Delegate dele = Delegate.CreateDelegate(ei.EventHandlerType, typeof(AllEventsInvoker).GetMethod(nameof(AllEventsInvoker.Catcher)));
                        ei.AddEventHandler(null, dele);
                    }
                }
                catch
                {
                    continue;
                }
            }
        }

        public static void RegisterMultipleEvents(MethodInfo function, params string[] eventName)
        {
            List<EventInfo> events = new List<EventInfo>();
            foreach (Type type in Assembly.GetAssembly(typeof(Exiled.Events.Events)).GetTypes())
                events.AddRange(type.GetEvents());
            foreach (EventInfo ei in events)
            {
                try
                {
                    if (eventName.Contains(ei.Name))
                    {
                        Delegate dele = Delegate.CreateDelegate(ei.EventHandlerType, function);
                        ei.AddEventHandler(null, dele);
                    }
                }
                catch
                {
                    continue;
                }
            }
        }

        private static void Catcher(IExiledEvent e)
        {
            AnyEventCalled?.Invoke(e);
        }
    }

    public static class API
    {
        private static DateTime HitokotoCooldown = DateTime.MinValue;

        public static ItemCategory GetCORRECTCategory(this ItemType type)
        {
            ItemCategory result = type.IsScp() ? ItemCategory.SCPItem : type.GetCategory();
            return result;
        }

        public static string GetGradientString(this string sentence, Color32 from, Color32 to)
        {
            double red_delta = to.r - from.r;
            double green_delta = to.g - from.g;
            double blue_delta = to.b - from.b;
            StringBuilder builder = new StringBuilder(sentence);
            for (int i = 0; i < sentence.Length; i++)
            {
                byte red = (byte)Math.Round(from.r + (red_delta * i / (sentence.Length - 1)));
                byte green = (byte)Math.Round(from.g + (green_delta * i / (sentence.Length - 1)));
                byte blue = (byte)Math.Round(from.b + (blue_delta * i / (sentence.Length - 1)));
                string hex = red.ToString("X2") + green.ToString("X2") + blue.ToString("X2");
                builder.Replace(sentence[i].ToString(), $"<color=#{hex}>{sentence[i]}</color>", i * 24, 1);
            }
            return builder.ToString();
        }

        public static string GetHitokoto(bool gradient)
        {
            string sentence = "今天没有查到一言，总之玩得开心！";
            if (DateTime.Now > HitokotoCooldown)
            {
                try
                {
                    sentence = GameplayData.Hitokoto[UnityEngine.Random.Range(0, GameplayData.Hitokoto.Length)];
                    if (gradient)
                        sentence = sentence.GetGradientString(new Color32(250, 128, 114, 255), new Color32(255, 105, 180, 255));
                }
                catch
                {
                    Log.Warn("获取一言失败，进入30秒冷却");
                    HitokotoCooldown = DateTime.Now.AddSeconds(30);
                }
            }
            return sentence;
        }

        public static string GetRoleName(this RoleTypeId role)
        {
            switch (role)
            {
                case RoleTypeId.ClassD:
                    return "D级人员";

                case RoleTypeId.Scientist:
                    return "科学家";

                case RoleTypeId.FacilityGuard:
                    return "设施保安";

                case RoleTypeId.NtfPrivate:
                    return "九尾狐列兵";

                case RoleTypeId.NtfSpecialist:
                    return "九尾狐收容专家";

                case RoleTypeId.NtfSergeant:
                    return "九尾狐中士";

                case RoleTypeId.NtfCaptain:
                    return "九尾狐指挥官";

                case RoleTypeId.ChaosConscript:
                    return "混沌征召者";

                case RoleTypeId.ChaosRifleman:
                    return "混沌步枪手";

                case RoleTypeId.ChaosRepressor:
                    return "混沌压制者";

                case RoleTypeId.ChaosMarauder:
                    return "混沌掠夺者";

                case RoleTypeId.Spectator:
                    return "观察者";

                case RoleTypeId.Tutorial:
                    return "教程角色";

                default:
                    return role.GetFullName();
            }
        }
        /// <summary>
        /// 获取语音频道文字
        /// </summary>
        /// <param name="player"></param>
        /// <returns></returns>
        public static string GetChannel(this VoiceChatChannel channel)
        {
            var text = GetChannel(channel, out string hex);
            return $"<color={hex}>{text}</color>";
        }
        public static string GetChannel(this VoiceChatChannel channel, out string hex)
        {
            switch (channel)
            {
                case VoiceChatChannel.Spectator:
                    hex = "#FFE4C4";
                    return "旁观";
                case VoiceChatChannel.Scp1576:
                    hex = "#D02090";
                    return "SCP-1576";
                case VoiceChatChannel.Intercom:
                    hex = "#00FFFF";
                    return "广播室";
                case VoiceChatChannel.Mimicry:
                    hex = "#FF7F24";
                    return "模仿音";
                case VoiceChatChannel.PreGameLobby:
                    hex = "#EEE9E9";
                    return "大厅";
                case VoiceChatChannel.RoundSummary:
                    hex = "#9370DB";
                    return "回合总结";
                case VoiceChatChannel.Radio:
                    hex = "#00BFFF";
                    return "无线电";
                case VoiceChatChannel.Proximity:
                    hex = "#00CED1";
                    return "区域";
                case VoiceChatChannel.ScpChat:
                    hex = "#FF5858";
                    return "SCP";
                default:
                    hex = "#FFFFFF";
                    return "N/A";
            }
        }
        /// <summary>
        /// 检测玩家是否正在瞄着某个玩家
        /// </summary>
        /// <param name="player">指定玩家</param>
        /// <param name="maxDistance">最大距离，0为无限制，玩家位置和被看玩家超过这个距离会判定为false</param>
        /// <returns>返回被瞄着的Player，如无，便返回null</returns>
        public static Player IsPointingAt(this Player player, float maxDistance = 10f)
        {
            Ray ray = new Ray(player.CameraTransform.position, player.CameraTransform.forward);
            Physics.Raycast(ray, out RaycastHit hitinfo);
            if (Vector3.Distance(player.Position, hitinfo.point) > maxDistance)
            {
                return null;
            }

            Player target = Player.Get(hitinfo.transform.GetComponentInParent<ReferenceHub>());
            if (target == player)
            {
                return null;
            }

            return target;
        }

        /// <summary>
        /// 仅HudInterface控件使用此方法
        /// 发Hint别用这个！
        /// 发Hint别用这个！
        /// 发Hint别用这个！
        /// 重要的事情说3遍
        /// </summary>
        public static void SendHint(this Player player, string content, uint duration)
        {
            player.HintDisplay.Show(new TextHint(content, new HintParameter[] { new StringHintParameter(content) }, null, duration));
        }
    }
}